# -*- coding: utf-8 -*-

from reportlab.lib import xmllib
import base64
import cStringIO
import string


#
# Использование:
# 
# metadata, body, binaries = parse(fb2_filename_string, enable_images_bool)
#
# Где fb2_filename_string -- строка, файл fb2
#     enable_images_bool  -- если True, то обрабатывать изображения в тексте, иначе изображения игнорируются
#
#
# metadata -- ассоциативный массив с метаданными книги. Содержит следующие ключи:
#   'title': Название книги
#   'first': Имя автора
#   'middle': Отчество автора
#   'last': Фамилия автора
#   'fi': Иницииал имени автора
#   'mi': Иницииал отчества автора
#   'li': Иницииал фамилии автора
#   'lang': Язык книги. ('ru' для русского, 'en' для английского.)
#
# body -- размеченный текст книги.
# Список объектов разметки, где объектом разметки могут быть следующие значения:
#
#   '':            Перенос страницы. ("Начать новую страницу")
#   '*':           Вертикальный пробел. (Как между концом параграфа и началом стихотворения, например.)
#   ['Text', t]:   Параграф с текстом. t -- строка, сожержащая текст параграфа.
#   ['Image', i]:  Изображение. i -- строка, ключ в ассоциативном массиве бинарных объектов.
#   ['H1', t]:     Заголовок первого уровня. (t -- строка с текстом заголовоком.)
#   ['H2', t]:     Заголовок второго уровня. (t -- строка с текстом заголовоком.)
#   ['H3', t]:     Заголовок третьего уровня. (t -- строка с текстом заголовоком.)
#   ['H4', t]:     Заголовок четвертого уровня. (t -- строка с текстом заголовоком.)
#   ['Em', t]:     Выделенный текст верхнего уровня, как для эпиграфов, например. (t -- строка с текстом.)
#   ['Verse', t]:  Строка стихотворения. (t -- строка с текстом строки. :))
#   ['Author', t]: Подпись авторства, как для эпиграфов или цитируемых писем. (t -- строка с подписью.)
#
#   Строки с текстами могут содержать, кроме собственно utf-8 текста, еще и теги html-подобной разметки:
# 
#     <a name="..."/>                : Невидимая метка для обозначения места в тексте. В атрибуте name строковая айди метки.
#     <font face="Bold">...</font>   : Переключение шрифта в полужирный режим.
#     <font face="Italic">...</font> : Переключение шрифта в наклонный режим. 
#     <font face="Mono">...</font>   : Переключение шрифта в моноширинный режим.
#                                      (Прим.: директивы '<font>' могут быть вложены друг в друга, например
#                                       '<font face="Bold"><font face="Italic">превед</font></font>'.)
#     <strike>...</stike>            : Перечеркнутый текст.
#     <sub>...</sub>                 : Нижний индекс. (Субскрипт.)
#     <sup>...</sup>                 : Верхний индекс. (Суперскрипт.)
#     <nobr>...</nobr>               : Внутри тега <nobr> запрещены автоматические переносы строк. ('Verbatim mode')
#     <br/>                          : Явный перевод строки.
#     <a href="...">...</a>          : Ссылка. В атрибуте href строковая айди метки.
#
#   Также в тексте могут содержаться следующие xml-entities:
#     
#     &amp;
#     &lt;
#     &gt;
#
# binaries -- ассоциативный массив бинарных объектов.
#   Ключи -- строки, айди объектов. (См. объект разметки 'Image' выше.)
#   Значения -- объекты типа cStringIO.StringIO, бинарное содержимое.
#
#

class FB2Parser(xmllib.XMLParser):

    def __init__(self, enableimages = True):
        xmllib.XMLParser.__init__(self)

        self.indesc = False
        self.intitleinfo = False
        self.intitle = False
        self.inauthor = False
        self.infname = False
        self.inmname = False
        self.inlname = False
        self.inlang = False       
        self.inbody = False
        self.binary = False
        self.binaryname = None
        self.hlevel = 0
        self.oldhlevel = 0
        self.inh = False
        self.inp = False
        self.inepi = False
        self.inhref = False
        self.incode = False
        self.do_id = []
        
        self.enableimages = enableimages
        self.binaries = {}
        self.encoding = 'utf-8'
        self.lang = ''

        self.title = ''
        self.author = ['', '', '']
        self.body = []

    def start_description(self, args):
        self.indesc = True

    def end_description(self):
        self.indesc = False

    def start_body(self, args):
        self.inbody = True

    def end_body(self):
        self.inbody = False
        self.body.append('')

    def start_author(self, args):
        if self.intitleinfo:
            self.inauthor = True

    def end_author(self):
        self.inauthor = False

    def start_lang(self, args):
        if self.intitleinfo:
            self.inlang = True

    def end_lang(self):
        self.inlang = False

    def start_title(self, args):
        self.inh = True

    def end_title(self):
        self.inh = False
        if self.hlevel == 0:
            self.body.append('')


    def _do_id(self, args):
        if 'id' in args:
            self.do_id.append('<a name="%s"/>' % args['id'])


    def start_epigraph(self, args):
        if self.inbody:
            self.inepi = True
            self._do_id(args)

    def end_epigraph(self):
        self.inepi = False
        if self.inbody: 
            self.body.append('*')
            self.body.append('*')

    def start_section(self, args):
        self.hlevel += 1
        if self.hlevel == 1 and 'id' not in args:
            self.body.append('')
        self._do_id(args)

    def end_section(self):
        self.hlevel -= 1

    def choose_style(self):
        if not self.inh:
            if self.inepi:
                return 'Em'
            else:
                return 'Text'

        if self.hlevel <= 1:
            return 'H1'
        elif self.hlevel == 2:
            return 'H2'
        elif self.hlevel == 3:
            return 'H3'
        else:
            return 'H4'

    def start_subtitle(self, args):
        if self.inbody:
            self.body.append(['H4', ''])
            self.inp = True
    
    def end_subtitle(self):
        self.inp = False

    def start_p(self, args):
        if self.inbody:
            self.body.append([self.choose_style(), ''])
            self.inp = True
            self._do_id(args)

    def end_p(self):
        self.inp = False


    ### HACK

    def start_th(self, args):
        if self.inbody:
            self.body.append(['Text', ''])
            self.inp = True
            self._do_id(args)

    def start_tr(self, args):
        self.start_th(args)

    def end_th(self):
        self.inp = False

    def end_tr(self):
        self.end_th()

    ### END HACK

    def start_image(self, args):
        if not self.inbody:
           return

        do_fakebreak = False

        if self.inp:
            # inline-images are not supported
            if not self.enableimages or 'alt' in args:
                # simply insert an 'alt' text if provided
                self._do_id(args)
                self.handle_data(args.get('alt', ''))
                return
            else:
                # make a fake paragraph break
                do_fakebreak = True

        # insert fake paragraph if the image has an anchor
        if self._do_id(args):
                
            if self.inp:
                # already in a paragraph, no need for a fake one.
                self.handle_data('')
            else:
                self.inp = True
                self.body.append(['Text', ''])
                if self.enableimages:
                    self.handle_data('')
                else:
                    self.handle_data(args.get('alt', ''))
                self.inp = False

        if self.enableimages:
            for k,v in args.iteritems():
                if k.endswith('href'):
                    self.body.append(['Image', v[1:]])
                    if do_fakebreak:
                        self.body.append([self.body[-2][0], ''])


    def end_image(self):
        if self.hlevel == 0:
            self.body.append('')

    def start_cite(self, args):
        if self.inbody: self._do_id(args)

    def end_cite(self):
        pass

    def start_poem(self, args):
        if self.inbody: self._do_id(args)

    def end_poem(self):
        pass

    def start_v(self, args):
        if self.inbody:
            self.body.append(['Verse', ''])
            self.inp = True

    def end_v(self):
        self.inp = False

    def start_stanza(self, args):
        if self.inbody: self.body.append('*')

    def end_stanza(self):
        if self.inbody: self.body.append('*')

    def start_strong(self, args):
        if self.inp: self.body[-1][1] += '<font face="Bold">'

    def end_strong(self):
        if self.inp: self.body[-1][1] += '</font>'

    def start_emphasis(self, args):
        if self.inp: self.body[-1][1] += '<font face="Italic">'

    def end_emphasis(self):
        if self.inp: self.body[-1][1] += '</font>'

    def start_strikethrough(self, args):
        if self.inp: self.body[-1][1] += '<strike>'

    def end_strikethrough(self):
        if self.inp: self.body[-1][1] += '</strike>'

    def start_sub(self, args):
        if self.inp: self.body[-1][1] += '<sub>'

    def end_sub(self):
        if self.inp: self.body[-1][1] += '</sub>'

    def start_sup(self, args):
        if self.inp: self.body[-1][1] += '<sup>'

    def end_sup(self):
        if self.inp: self.body[-1][1] += '</sup>'

    def start_code(self, args):
        if self.inp: 
            self.body[-1][1] += '<nobr><font name="Mono">'
            self.incode = True

    def end_code(self):
        if self.inp: 
            self.body[-1][1] += '</font name="Mono"></nobr>'
            self.incode = False

    def start_a(self, args):
        if self.inbody:
            for k,v in args.iteritems():
                if k.endswith('href'):
                    self.inhref = True
                    self.body[-1][1] += ('<a href="%s">' % v)

    def end_a(self):
        if self.inhref: 
            self.body[-1][1] += '</a>'
            self.inhref = False


    def start_binary(self, args):
        self.binary = ''
        self.binaryname = args['id']

    def end_binary(self):
        self.binaries[self.binaryname] = cStringIO.StringIO(base64.b64decode(self.binary))
        self.binary = False
        self.binaryname = None

    def unknown_starttag(self, tag, args):
        if tag == 'title-info':
            self.intitleinfo = True
        elif self.intitleinfo and tag == 'book-title':
            self.intitle = True
        elif self.inauthor and tag == 'first-name':
            self.infname = True
        elif self.inauthor and tag == 'middle-name':
            self.inmname = True
        elif self.inauthor and tag == 'last-name':
            self.inlname = True
        elif self.inbody and tag == 'text-author':
            self.body.append(['Author', ''])
            self.inp = True


    def unknown_endtag(self, tag):
        if tag == 'title-info':
            self.intitleinfo = False
        elif tag == 'book-title':
            self.intitle = False
        elif tag == 'first-name':
            self.infname = False
        elif tag == 'middle-name':
            self.inmname = False
        elif tag == 'last-name':
            self.inlname = False
        elif tag == 'empty-line':
            if self.inbody: self.body.append('*')
        elif self.inbody and tag == 'text-author':
            self.inp = False

    def handle_data(self, data):

        if self.encoding:
            data = unicode(data, self.encoding).encode('utf-8')

        data = data.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')

        # HACK
        # bug in fictionbook spec

        if self.incode:
            # non-breaking space
            data = data.replace('\n', '<br/>').replace('\r', '').replace(' ', '\xc2\xa0')

        if self.intitle:
            self.title += data
        elif self.infname:
            self.author[0] += data
        elif self.inmname:
            self.author[1] += data
        elif self.inlname:
            self.author[2] += data
        elif self.inlang:
            self.lang += data

        elif self.binary != False:
            self.binary += data

        elif self.inp:
            if len(self.do_id) > 0:
                self.body[-1][1] += ''.join(self.do_id)
                self.do_id = []

            self.body[-1][1] += data


    def handle_cdata(self, data):
        self.handle_data(self, data)

    def handle_proc(self, name, data):
        print '[%s] [%s]' % (name, data)
        d = {}
        for x in data.split():
            tmp = x.split('=')
            if len(tmp) == 2:
                d[tmp[0]] = tmp[1].replace('"', '')
        if 'encoding' in d and d['encoding'].lower() != 'utf-8':
            self.encoding = d['encoding'].lower()

    def handle_charref(self, name):
        try:
            if name[0] == 'x':
                n = string.atoi(name[1:], 16)
            else:
                n = string.atoi(name)
        except string.atoi_error:
            #self.unknown_charref(name)
            return
        self.handle_data(unichr(n).encode('utf-8'))



def test(filen):
    p = FB2Parser()
    p.feed(open(filen).read())
    p.close()
    print ' '.join(p.author), p.title

def parse(filen, enableimages):
    p = FB2Parser(enableimages)
    p.feed(open(filen).read())
    p.close()

    return {'title': p.title, 
            'first': p.author[0], 
            'middle': p.author[1], 
            'last': p.author[2], 
            'lang': p.lang,
            'fi': unicode(p.author[0], 'utf-8')[:1].encode('utf-8'), 
            'mi': unicode(p.author[1], 'utf-8')[:1].encode('utf-8'),
            'li': unicode(p.author[2], 'utf-8')[:1].encode('utf-8')}, p.body, p.binaries


if __name__=='__main__':
    import sys
    test(sys.argv[1])
